/*
 * Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.snmp;

import com.sun.jmx.snmp.SnmpSecurityParameters;
// java imports
//
import java.util.Vector;
import java.net.InetAddress;


import com.sun.jmx.snmp.SnmpStatusException;

/**
 * A partially decoded representation of an SNMP packet. It contains
 * the information contained in any SNMP message (SNMPv1, SNMPv2 or
 * SNMPv3).
 * <p><b>This API is a Sun Microsystems internal API  and is subject
 * to change without notice.</b></p>
 *
 * @since 1.5
 */
public abstract class SnmpMsg implements SnmpDefinitions {

  /**
   * The protocol version.
   * <P><CODE>decodeMessage</CODE> and <CODE>encodeMessage</CODE> do not
   * perform any check on this value.
   * <BR><CODE>decodeSnmpPdu</CODE> and <CODE>encodeSnmpPdu</CODE> only
   * accept  the values 0 (for SNMPv1), 1 (for SNMPv2) and 3 (for SNMPv3).
   */
  public int version = 0;

  /**
   * Encoding of the PDU.
   * <P>This is usually the BER encoding of the PDU's syntax
   * defined in RFC1157 and RFC1902. However, this can be authenticated
   * or encrypted data (but you need to implemented your own
   * <CODE>SnmpPduFactory</CODE> class).
   */
  public byte[] data = null;

  /**
   * Number of useful bytes in the <CODE>data</CODE> field.
   */
  public int dataLength = 0;

  /**
   * Source or destination address.
   * <BR>For an incoming message it's the source.
   * For an outgoing message it's the destination.
   */
  public InetAddress address = null;

  /**
   * Source or destination port.
   * <BR>For an incoming message it's the source.
   * For an outgoing message it's the destination.
   */
  public int port = 0;
  /**
   * Security parameters. Contain informations according to Security Model (Usm, community string
   * based, ...).
   */
  public SnmpSecurityParameters securityParameters = null;

  /**
   * Returns the encoded SNMP version present in the passed byte array.
   *
   * @param data The unmarshalled SNMP message.
   * @return The SNMP version (0, 1 or 3).
   */
  public static int getProtocolVersion(byte[] data)
      throws SnmpStatusException {
    int version = 0;
    BerDecoder bdec = null;
    try {
      bdec = new BerDecoder(data);
      bdec.openSequence();
      version = bdec.fetchInteger();
    } catch (BerException x) {
      throw new SnmpStatusException("Invalid encoding");
    }
    try {
      bdec.closeSequence();
    } catch (BerException x) {
    }
    return version;
  }

  /**
   * Returns the associated request ID.
   *
   * @param data The flat message.
   * @return The request ID.
   */
  public abstract int getRequestId(byte[] data) throws SnmpStatusException;

  /**
   * Encodes this message and puts the result in the specified byte array.
   * For internal use only.
   *
   * @param outputBytes An array to receive the resulting encoding.
   * @throws ArrayIndexOutOfBoundsException If the result does not fit into the specified array.
   */
  public abstract int encodeMessage(byte[] outputBytes)
      throws SnmpTooBigException;

  /**
   * Decodes the specified bytes and initializes this message.
   * For internal use only.
   *
   * @param inputBytes The bytes to be decoded.
   * @throws SnmpStatusException If the specified bytes are not a valid encoding.
   */
  public abstract void decodeMessage(byte[] inputBytes, int byteCount)
      throws SnmpStatusException;

  /**
   * Initializes this message with the specified <CODE>pdu</CODE>.
   * <P>
   * This method initializes the data field with an array of
   * <CODE>maxDataLength</CODE> bytes. It encodes the <CODE>pdu</CODE>.
   * The resulting encoding is stored in the data field
   * and the length of the encoding is stored in <CODE>dataLength</CODE>.
   * <p>
   * If the encoding length exceeds <CODE>maxDataLength</CODE>,
   * the method throws an exception.
   *
   * @param pdu The PDU to be encoded.
   * @param maxDataLength The maximum length permitted for the data field.
   * @throws SnmpStatusException If the specified <CODE>pdu</CODE> is not valid.
   * @throws SnmpTooBigException If the resulting encoding does not fit into
   * <CODE>maxDataLength</CODE> bytes.
   * @throws ArrayIndexOutOfBoundsException If the encoding exceeds <CODE>maxDataLength</CODE>.
   */
  public abstract void encodeSnmpPdu(SnmpPdu pdu, int maxDataLength)
      throws SnmpStatusException, SnmpTooBigException;


  /**
   * Gets the PDU encoded in this message.
   * <P>
   * This method decodes the data field and returns the resulting PDU.
   *
   * @return The resulting PDU.
   * @throws SnmpStatusException If the encoding is not valid.
   */
  public abstract SnmpPdu decodeSnmpPdu()
      throws SnmpStatusException;

  /**
   * Dumps the content of a byte buffer using hexadecimal form.
   *
   * @param b The buffer to dump.
   * @param offset The position of the first byte to be dumped.
   * @param len The number of bytes to be dumped starting from offset.
   * @return The string containing the dump.
   */
  public static String dumpHexBuffer(byte[] b, int offset, int len) {
    StringBuffer buf = new StringBuffer(len << 1);
    int k = 1;
    int flen = offset + len;

    for (int i = offset; i < flen; i++) {
      int j = b[i] & 0xFF;
      buf.append(Character.forDigit((j >>> 4), 16));
      buf.append(Character.forDigit((j & 0x0F), 16));
      k++;
      if (k % 16 == 0) {
        buf.append('\n');
        k = 1;
      } else {
        buf.append(' ');
      }
    }
    return buf.toString();
  }

  /**
   * Dumps this message in a string.
   *
   * @return The string containing the dump.
   */
  public String printMessage() {
    StringBuffer sb = new StringBuffer();
    sb.append("Version: ");
    sb.append(version);
    sb.append("\n");
    if (data == null) {
      sb.append("Data: null");
    } else {
      sb.append("Data: {\n");
      sb.append(dumpHexBuffer(data, 0, dataLength));
      sb.append("\n}\n");
    }

    return sb.toString();
  }

  /**
   * For SNMP Runtime private use only.
   */
  public void encodeVarBindList(BerEncoder benc,
      SnmpVarBind[] varBindList)
      throws SnmpStatusException, SnmpTooBigException {
    //
    // Remember: the encoder does backward encoding
    //
    int encodedVarBindCount = 0;
    try {
      benc.openSequence();
      if (varBindList != null) {
        for (int i = varBindList.length - 1; i >= 0; i--) {
          SnmpVarBind bind = varBindList[i];
          if (bind != null) {
            benc.openSequence();
            encodeVarBindValue(benc, bind.value);
            benc.putOid(bind.oid.longValue());
            benc.closeSequence();
            encodedVarBindCount++;
          }
        }
      }
      benc.closeSequence();
    } catch (ArrayIndexOutOfBoundsException x) {
      throw new SnmpTooBigException(encodedVarBindCount);
    }
  }

  /**
   * For SNMP Runtime private use only.
   */
  void encodeVarBindValue(BerEncoder benc,
      SnmpValue v) throws SnmpStatusException {
    if (v == null) {
      benc.putNull();
    } else if (v instanceof SnmpIpAddress) {
      benc.putOctetString(((SnmpIpAddress) v).byteValue(), SnmpValue.IpAddressTag);
    } else if (v instanceof SnmpCounter) {
      benc.putInteger(((SnmpCounter) v).longValue(), SnmpValue.CounterTag);
    } else if (v instanceof SnmpGauge) {
      benc.putInteger(((SnmpGauge) v).longValue(), SnmpValue.GaugeTag);
    } else if (v instanceof SnmpTimeticks) {
      benc.putInteger(((SnmpTimeticks) v).longValue(), SnmpValue.TimeticksTag);
    } else if (v instanceof SnmpOpaque) {
      benc.putOctetString(((SnmpOpaque) v).byteValue(), SnmpValue.OpaqueTag);
    } else if (v instanceof SnmpInt) {
      benc.putInteger(((SnmpInt) v).intValue());
    } else if (v instanceof SnmpString) {
      benc.putOctetString(((SnmpString) v).byteValue());
    } else if (v instanceof SnmpOid) {
      benc.putOid(((SnmpOid) v).longValue());
    } else if (v instanceof SnmpCounter64) {
      if (version == snmpVersionOne) {
        throw new SnmpStatusException("Invalid value for SNMP v1 : " + v);
      }
      benc.putInteger(((SnmpCounter64) v).longValue(), SnmpValue.Counter64Tag);
    } else if (v instanceof SnmpNull) {
      int tag = ((SnmpNull) v).getTag();
      if ((version == snmpVersionOne) && (tag != SnmpValue.NullTag)) {
        throw new SnmpStatusException("Invalid value for SNMP v1 : " + v);
      }
      if ((version == snmpVersionTwo) &&
          (tag != SnmpValue.NullTag) &&
          (tag != SnmpVarBind.errNoSuchObjectTag) &&
          (tag != SnmpVarBind.errNoSuchInstanceTag) &&
          (tag != SnmpVarBind.errEndOfMibViewTag)) {
        throw new SnmpStatusException("Invalid value " + v);
      }
      benc.putNull(tag);
    } else {
      throw new SnmpStatusException("Invalid value " + v);
    }

  }


  /**
   * For SNMP Runtime private use only.
   */
  public SnmpVarBind[] decodeVarBindList(BerDecoder bdec)
      throws BerException {
    bdec.openSequence();
    Vector<SnmpVarBind> tmp = new Vector<SnmpVarBind>();
    while (bdec.cannotCloseSequence()) {
      SnmpVarBind bind = new SnmpVarBind();
      bdec.openSequence();
      bind.oid = new SnmpOid(bdec.fetchOid());
      bind.setSnmpValue(decodeVarBindValue(bdec));
      bdec.closeSequence();
      tmp.addElement(bind);
    }
    bdec.closeSequence();
    SnmpVarBind[] varBindList = new SnmpVarBind[tmp.size()];
    tmp.copyInto(varBindList);
    return varBindList;
  }


  /**
   * For SNMP Runtime private use only.
   */
  SnmpValue decodeVarBindValue(BerDecoder bdec)
      throws BerException {
    SnmpValue result = null;
    int tag = bdec.getTag();

    // bugId 4641696 : RuntimeExceptions must be transformed in
    //                 BerException.
    switch (tag) {

      //
      // Simple syntax
      //
      case BerDecoder.IntegerTag:
        try {
          result = new SnmpInt(bdec.fetchInteger());
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpInt from decoded value.");
        }
        break;
      case BerDecoder.OctetStringTag:
        try {
          result = new SnmpString(bdec.fetchOctetString());
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpString from decoded value.");
        }
        break;
      case BerDecoder.OidTag:
        try {
          result = new SnmpOid(bdec.fetchOid());
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpOid from decoded value.");
        }
        break;
      case BerDecoder.NullTag:
        bdec.fetchNull();
        try {
          result = new SnmpNull();
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpNull from decoded value.");
        }
        break;

      //
      // Application syntax
      //
      case SnmpValue.IpAddressTag:
        try {
          result = new SnmpIpAddress(bdec.fetchOctetString(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpIpAddress from decoded value.");
        }
        break;
      case SnmpValue.CounterTag:
        try {
          result = new SnmpCounter(bdec.fetchIntegerAsLong(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpCounter from decoded value.");
        }
        break;
      case SnmpValue.GaugeTag:
        try {
          result = new SnmpGauge(bdec.fetchIntegerAsLong(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpGauge from decoded value.");
        }
        break;
      case SnmpValue.TimeticksTag:
        try {
          result = new SnmpTimeticks(bdec.fetchIntegerAsLong(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpTimeticks from decoded value.");
        }
        break;
      case SnmpValue.OpaqueTag:
        try {
          result = new SnmpOpaque(bdec.fetchOctetString(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpOpaque from decoded value.");
        }
        break;

      //
      // V2 syntaxes
      //
      case SnmpValue.Counter64Tag:
        if (version == snmpVersionOne) {
          throw new BerException(BerException.BAD_VERSION);
        }
        try {
          result = new SnmpCounter64(bdec.fetchIntegerAsLong(tag));
        } catch (RuntimeException r) {
          throw new BerException();
          // BerException("Can't build SnmpCounter64 from decoded value.");
        }
        break;

      case SnmpVarBind.errNoSuchObjectTag:
        if (version == snmpVersionOne) {
          throw new BerException(BerException.BAD_VERSION);
        }
        bdec.fetchNull(tag);
        result = SnmpVarBind.noSuchObject;
        break;

      case SnmpVarBind.errNoSuchInstanceTag:
        if (version == snmpVersionOne) {
          throw new BerException(BerException.BAD_VERSION);
        }
        bdec.fetchNull(tag);
        result = SnmpVarBind.noSuchInstance;
        break;

      case SnmpVarBind.errEndOfMibViewTag:
        if (version == snmpVersionOne) {
          throw new BerException(BerException.BAD_VERSION);
        }
        bdec.fetchNull(tag);
        result = SnmpVarBind.endOfMibView;
        break;

      default:
        throw new BerException();

    }

    return result;
  }

}
